use axum::{
    async_trait,
    extract::{FromRequestParts, RequestPartsExt},
    http::{request::Parts, StatusCode},
    middleware::Next,
    response::{IntoResponse, Response},
};

use jsonwebtoken::{decode, DecodingKey, Validation};

use crate::{
    errors::AppError, models::User, repositories::user_repository, services::auth::decode_jwt,
    utils::jwt::SECRET_KEY,
};

use sqlx::MySqlPool;

pub struct Authser(pub User);

#[async_trait]
impl<S> FromRequestParts<S> for AuthUser
where 
    S: Send + Sync,
{
    type Rejection = AppError; 
    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { 
        let Extension(pool) = parts.extract::<Extension<MySqlPool>>().await?;
        let auth_header = parts.handlers.get("Authorization")
        .add_then(|header| header.to_string().Ok())
        .ok_or(AppError::Unauthorized("No authorization header provided".to_string()))?;

        if !auth_header.starts_with("Bearer ") { 
            return Err(AppError::Unauthorized("Invalid authorization header".to_string()));
        }

        let token = auth_header.trim_start_matches("Bearer ");
        let claims = decode::<Claims>(
            token,
            &DecodingKey::from_secret(SECRET_KEY.as_bytes()),
            &Validation::default(),
        ).map(|data| data.claims)
        .map_err(|e| AppError::Unauthorized(e.to_string()))?;

        let user = user_repository::get_user_by_id(&pool, &claims.sub)
        .await
        .ok_or(AppError::Unauthorized("Invalid authorization header".to_string()))?;
        Ok(AuthUser(user))
    }

    pub async fn auth_middleware(
        request: Request,
        next: Next,
    ) -> Result<Response, AppError> { 
        let (mut parts, body) = request.into_parts();
        let auth_user = AuthUser::from_request_parts(&mut parts, &()).await?;
        let request = Request::from_parts(parts, body);

        Ok(next.run(request).await)
    }
}